package com.itfreer.gis.server.tms;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import com.itfreer.gis.server.mercator.MercatorProjection;
import com.itfreer.gis.server.storage.MinIOStorage;
import com.itfreer.gis.server.utils.MapDataUtils;
import com.itfreer.gis.server.utils.TableInfo;
import org.geotools.geojson.geom.GeometryJSON;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.itfreer.form.api.dictionary.DictionaryService;
import com.itfreer.gis.server.mercator.TileUtils;
import com.itfreer.gis.server.vector.FeatureDao;
import com.itfreer.gis.server.vector.generate.VectorTileGenerate;
import com.itfreer.gis.server.vector.mapbox.MapBoxTileBuilderFactory;

/**
 * TMS服务发布接口
 */
@Component
public class DTMSServerImp implements DTMSServer {

    @Autowired
    private FeatureDao featureDao;
    @Autowired
    private VectorTileGenerate vectorTileGenerate;
    // 切片构建工厂
    private MapBoxTileBuilderFactory factory = new MapBoxTileBuilderFactory();
    @Autowired
    private DictionaryService dictionaryManage;

    @Autowired
    private MinIOStorage minIOStorage;

    // 动态切片服务器的存储块
    private String dtmsBucket = "dtms";

    /**
     * 地图切片
     */
    @Override
    public byte[] generate(String serverID, int z, int x, int y) {
        TableInfo tableInfo = MapDataUtils.getMapInfo(serverID, dictionaryManage);
        if (tableInfo == null) {
            return null;
        }

        String key = serverID + "/" + String.valueOf(z) + "/" + String.valueOf(x) + "/" + String.valueOf(y) + ".pbf";
        // 无缓存切切片添加缓存
        try {
            InputStream inputStream = minIOStorage.getObject(dtmsBucket, key);
            if (inputStream == null) {
                vectorTileGenerate.setTileBuilderFactory(factory);
                final CoordinateReferenceSystem sourceCrs = featureDao
                        .getCoordinateReferenceSystem(tableInfo.tableName);
                final String fields = featureDao.fields(tableInfo.tableName);
                byte[] bpf = vector(tableInfo.tableName, fields, sourceCrs, x, y, z);
                addTileCache(bpf, key);
                return bpf;
            } else {
                return toByteArray(inputStream);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    /**
     * 子线程切片
     *
     * @param source
     * @param fields
     * @param sourceCrs
     * @param x
     * @param y
     * @param z
     * @throws Exception
     */
    private byte[] vector(String source, String fields, CoordinateReferenceSystem sourceCrs, int x, int y, int z)
            throws Exception {
        List<Map<String, Object>> features = queryFeature(source, fields, x, y, z);
        if (features == null || features.size() <= 0) {
            return null;
        }
        return vectorTileGenerate.generate(source, sourceCrs, features, x, y, z);
    }

    /**
     * 查询数据
     *
     * @param source
     * @param x
     * @param y
     * @param z
     * @return
     * @throws Exception
     */
    private List<Map<String, Object>> queryFeature(String source, String fields, int x, int y, int z) throws Exception {
        String extent = TileUtils.parseXyz2Bound(x, y, z);
        List<Map<String, Object>> results = featureDao.queryFeature(source, fields, extent);
        return results;
    }

    /**
     * 添加切片缓存
     *
     * @param pbf
     * @param key
     */
    private void addTileCache(byte[] pbf, String key) {
        if (pbf != null) {
            try {
                InputStream dfpStream = new ByteArrayInputStream(pbf);
                minIOStorage.putObject(dtmsBucket, key, dfpStream, factory.getMimeType());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 清除切片缓存
     *
     * @param geoJson
     * @param serverName
     * @throws Exception
     */
    @Override
    public void clearTileCache(String geoJson, String serverName) throws Exception {

        GeometryJSON geometryJson = new GeometryJSON(6);
        Geometry originalGeom = geometryJson.read(geoJson);
        Coordinate[] points = originalGeom.getEnvelope().getCoordinates();
        Envelope bounds = null;
        if (points.length == 1){
            bounds = new Envelope(points[0]);
        }else if (points.length >= 3){
            bounds = new Envelope(points[0], points[2]);
        }

        if (bounds != null){
            double[] extent = new double[]{bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()};

            int minLevel = 1;
            int maxLevel = 19;

            // 2.计算切片范围
            for (int z = minLevel; z <= maxLevel; z++) {

                // 起始结束列
                int minX = MercatorProjection.longitudeToTileX(extent[0], z);
                int maxX = MercatorProjection.longitudeToTileX(extent[2], z);

                // 起始结束行
                int minY = MercatorProjection.latitudeToTileY(extent[3], z);
                int maxY = MercatorProjection.latitudeToTileY(extent[1], z);

                for (int x = minX; x <= maxX; x++) {
                    for (int y = minY; y <= maxY; y++) {
                        String objectName = serverName + "/" + String.valueOf(z) + "/" + String.valueOf(x) + "/"
                                + String.valueOf(y) + ".pbf";
                        minIOStorage.removeObject(dtmsBucket, objectName);
                    }
                }
            }
        }

    }
}
